<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片 OCR</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: Arial, sans-serif;
            padding: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #f5f5f5;
        }

        .main-container {
            width: 90%;
            max-width: 1600px;
            height: 90vh;
            border: 1px solid #ccc;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            background-color: white;
            overflow: hidden;
        }

        .container {
            display: flex;
            gap: 20px;
            height: 100%;
            padding: 20px;
        }

        .left-column {
            flex: 3;
            display: flex;
            flex-direction: column;
            gap: 20px;
        }

        .right-column {
            flex: 2;
            display: flex;
            flex-direction: column;
            gap: 20px;
        }

        .left-top {
            flex: 1;
            display: flex;
            gap: 20px;
        }

        .left-top-left,
        .left-top-right {
            flex: 1;
            border: 1px solid #ddd;
            padding: 15px;
            border-radius: 5px;
            overflow: hidden;
        }

        .left-top-right textarea {
            width: 100%;
            height: calc(100% - 45px);
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            max-height: 500px;
            resize: vertical;
        }

        .left-bottom {
            flex: 1;
            border: 1px solid #ddd;
            padding: 15px;
            border-radius: 5px;
            display: flex;
            flex-direction: column;
        }

        .right-top {
            flex: 2;
            border: 1px solid #ddd;
            padding: 15px;
            border-radius: 5px;
            overflow: auto;
        }

        .right-bottom {
            flex: 3;
            border: 1px solid #ddd;
            padding: 15px;
            border-radius: 5px;
            overflow: auto;
        }

        .section-title {
            margin-bottom: 10px;
            padding-bottom: 10px;
            border-bottom: 1px solid #eee;
            color: #333;
        }

        .image-container {
            flex: 1;
            display: flex;
            justify-content: center;
            align-items: center;
            background-color: #f5f5f5;
            color: #999;
            margin-top: 10px;
            border-radius: 4px;
        }

        .image-container img {
            max-width: 100%;
            max-height: 100%;
            object-fit: contain;
        }

        textarea {
            width: 100%;
            height: calc(100% - 50px);
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            resize: none;
        }

        ul {
            list-style-type: none;
            padding-left: 0;
            max-height: 350px;
            overflow: auto;
        }

        li {
            padding: 5px 0;
            border-bottom: 1px solid #eee;
        }

        .left-top-left ul li input {
            width: 100%;
            height: 100%;
            padding: 2px;
            border: 0;
        }

        .left-top-left ul li select {
            width: 100%;
            height: 100%;
            padding: 2px;
            border: 0;
        }

        .btn {
            cursor: pointer;
        }

        .btn:hover {
            background-color: #2980b9;
            border-radius: 5px;
        }

        .btnDisabled {
            pointer-events: none;
            display: inline-block;
            background-color: #b2d5ec;
            background-image: radial-gradient(circle at center, rgba(255, 255, 255, 0.8) 10%, transparent 50%);
        }
    </style>
</head>

<body>
    <script>
        function valueLocalStorage(id_name, default_content = "") {
            const element = document.getElementById(id_name);
            element.value = localStorage.getItem(id_name) || default_content;
            element.addEventListener("change", function (event) {
                localStorage.setItem(id_name, event.target.value);
                showNotification(`localStorage 保存：${id_name}：${event.target.value}`, "success");
            })
        }
        async function copyToClipboard(content, successMsg = '内容已复制到剪贴板！', errorMsg = '复制失败，请手动复制') {
            try {
                let textToCopy;

                // 判断content是元素ID还是直接文本
                if (typeof content === null) {
                    showNotification('复制失败，没有识别出的内容', 'error');
                    return false;
                } else {
                    // 否则直接使用content
                    textToCopy = content;
                }

                // 使用现代Clipboard API
                if (navigator.clipboard && window.isSecureContext) {
                    await navigator.clipboard.writeText(textToCopy);
                } else {
                    // 降级方案：使用document.execCommand
                    const textArea = document.createElement('textarea');
                    textArea.value = textToCopy;
                    textArea.style.position = 'fixed';
                    textArea.style.opacity = '0';
                    document.body.appendChild(textArea);
                    textArea.select();
                    document.execCommand('copy');
                    document.body.removeChild(textArea);
                }

                // 显示成功消息
                showNotification(successMsg, 'success');
                return true;
            } catch (err) {
                console.error('复制失败:', err);
                showNotification(errorMsg, 'error');
                return false;
            }
        }
    </script>
    <div class="main-container">
        <div class="container">
            <!-- 左列 (3份) -->
            <div class="left-column">
                <!-- 左列上半部分 -->
                <div class="left-top">
                    <div class="left-top-left">
                        <h3 class="section-title">参数及功能</h3>
                        <ul>
                            <li><input id="ollama-url" value=""></input></li>
                            <script>
                                valueLocalStorage("ollama-url", "http://localhost:11434");
                            </script>
                            <li><select id="ollama-models-name">
                                    <option value="a">a</option>
                                    <option value="b">b</option>
                                </select></li>
                            <script>
                                const ollama_model_name = document.getElementById('ollama-models-name');
                                async function fetchTags() {
                                    try {
                                        const response = await fetch(document.getElementById('ollama-url').value + "/api/tags");
                                        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
                                        const data = await response.json();
                                        ollama_model_name.innerHTML = "";
                                        data.models.forEach(model => {
                                            const option = document.createElement('option');
                                            if (model.name === "qwen3:0.6b") {
                                                option.selected = true;  // 设置选中状态
                                            }
                                            option.value = model.name;
                                            option.textContent = `模型：${model.name}，大小：${Math.floor(model.size / 1024 / 1024)} Mb`;
                                            ollama_model_name.append(option);
                                        });
                                    } catch (error) {
                                        console.error('Error fetching data:', error);
                                    }
                                }
                                fetchTags();
                            </script>
                            <li class="btn">下载对话</li>
                            <li>1.在OCR区域粘贴图片，或者拖动图片到该区域。</li>
                            <li>2.点击“OCR”标题，与Tesseract服务器进行识别通讯。</li>
                            <li>3.点击“Ollama Chat”标题，与Ollama服务器进行对话。</li>
                            <li>4.点击“下载对话”标题，将数据保存到JSON变量，并下载。</li>
                            <li>5.点击“识别内容”标题，复制OCR内容到剪贴板。</li>
                            <script>
                            </script>
                        </ul>
                    </div>
                    <div class="left-top-right">
                        <h3 class="section-title">系统提示词</h3>
                        <textarea id="system-prompt" placeholder="在此输入系统提示词内容..."></textarea>
                    </div>
                    <script>
                        const system_prompt = document.getElementById('system-prompt');
                        valueLocalStorage("system-prompt", "你是一个简洁的AI智能助手。");
                    </script>
                </div>

                <!-- 左列下半部分 -->
                <div id="left-bottom" class="left-bottom">
                    <h3 class="section-title btn">OCR</h3>
                    <div id="image-container" class="image-container"></div>
                </div>
                <script>
                    class InputImage {
                        constructor() {
                            this.pasteArea = document.getElementById('left-bottom');
                            this.previewImg = document.getElementById('image-container');
                            this.blob = null;
                            this.img = document.createElement("img");
                            this.tesseract_ocr_result = "";
                            this.elementListener(this);
                        }
                        elementListener(someVariable) {
                            someVariable.pasteArea.addEventListener('paste', function (e) {
                                if (e.clipboardData && e.clipboardData.items) {
                                    const items = e.clipboardData.items;
                                    for (let i = 0; i < items.length; i++) {
                                        if (items[i].type.indexOf('image') !== -1) {
                                            someVariable.blob = items[i].getAsFile();
                                            const url = URL.createObjectURL(someVariable.blob);
                                            someVariable.img.src = url;
                                            someVariable.img.width = someVariable.previewImg.clientWidth;
                                            someVariable.img.height = someVariable.previewImg.clientHeight;
                                            someVariable.previewImg.innerHTML = "";
                                            someVariable.previewImg.append(someVariable.img);
                                            break;
                                        }
                                    }
                                }
                            });
                            someVariable.pasteArea.addEventListener('dragover', function (e) {
                                e.preventDefault();
                                someVariable.pasteArea.classList.add('dragover');
                            });
                            someVariable.pasteArea.addEventListener('dragleave', function () {
                                someVariable.pasteArea.classList.remove('dragover');
                            });
                            someVariable.pasteArea.addEventListener('drop', function (e) {
                                e.preventDefault();
                                someVariable.pasteArea.classList.remove('dragover');
                                const files = e.dataTransfer.files;
                                if (files.length) {
                                    const file = files[0];
                                    if (file.type.indexOf('image') !== -1) {
                                        someVariable.blob = file;
                                        const url = URL.createObjectURL(file);
                                        someVariable.img.src = url;
                                        someVariable.img.width = someVariable.previewImg.clientWidth;
                                        someVariable.img.height = someVariable.previewImg.clientHeight;
                                        someVariable.previewImg.innerHTML = "";
                                        someVariable.previewImg.append(someVariable.img);
                                    } else if (file.type.indexOf('json') !== -1) {
                                        const reader = new FileReader();
                                        reader.onload = (e) => {
                                            try {
                                                const chats = JSON.parse(e.target.result);
                                                console.log(chats)
                                                showNotification("JSON 内容：\n" + JSON.stringify(chats, null, 2), "error");
                                            } catch (error) {
                                                console.error("无法解析JSON文件:", error);
                                                showNotification("无法解析JSON文件：" + error, "error");
                                            }
                                        };
                                        reader.onerror = (e) => {
                                            console.error("读取文件时出错:", e);
                                            showNotification("读取文件时出错：" + e, "error");
                                        };
                                        reader.readAsText(file);
                                    }
                                }
                            });
                        }
                        async blobOcr() {
                            const imageFile = this.blob;
                            if (!imageFile) return;

                            const formData = new FormData();
                            formData.append('image', imageFile);
                            const recognizeText = document.getElementById("recognize-text");

                            try {
                                const response = await fetch('/ocr', {
                                    method: 'POST',
                                    body: formData
                                });

                                const result = await response.json();
                                if (result.success) {
                                    this.tesseract_ocr_result = result;
                                    tesseract_ocr_result.value = result.text;
                                    showNotification("Tesseract OCR 完成！", "success");
                                } else {
                                    showNotification(JSON.stringify({ "识别失败": result.error }, null, 2), "error");
                                }
                            } catch (error) {
                                showNotification(JSON.stringify({ "发生错误": error.message }, null, 2), "error");
                            } finally {
                                console.log("识别图片中的文字");
                            }
                        }
                    }
                    const II = new InputImage();
                </script>
            </div>

            <!-- 右列 (2份) -->
            <div class="right-column">
                <!-- 右列上半部分 -->
                <div class="right-top">
                    <h3 class="section-title btn">识别内容</h3>
                    <textarea id="tesseract-ocr" placeholder="Tesseract OCR内容..."></textarea>
                </div>
                <script>
                    const tesseract_ocr_result = document.getElementById("tesseract-ocr");
                </script>

                <!-- 右列下半部分 -->
                <div class="right-bottom">
                    <h3 id="ollamaStreamChatBtn" class="section-title btn">Ollama Chat</h3>
                    <textarea id="ollama-chat" placeholder="Ollama Chat内容..."></textarea>
                </div>
            </div>
        </div>
    </div>
    <script>
        const ollama_chat_result = document.getElementById("ollama-chat");
        const ollamaStreamChatBtn = document.getElementById("ollamaStreamChatBtn");
        async function ollamaStreamChat() {
            if (tesseract_ocr_result === null) {
                showNotification('没有OCR识别的内容', 'error');
                return false;
            }
            const payload = {
                model: ollama_model_name.value,
                messages: [
                    { role: "system", content: system_prompt.value },
                    { role: "user", content: tesseract_ocr_result.value }
                ],
                stream: true
            };
            ollamaStreamChatBtn.classList.add("btnDisabled");
            ollama_chat_result.value = "";
            const response = await fetch(document.getElementById('ollama-url').value + "/api/chat", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Accept": "text/event-stream"
                },
                body: JSON.stringify(payload)
            });
            const reader = response.body.getReader();
            const decoder = new TextDecoder();
            let buffer = "";
            let messages = "";
            while (true) {
                const { done, value } = await reader.read();
                if (done) break;
                buffer += decoder.decode(value, { stream: true });
                const chunks = buffer.split("\n");
                buffer = chunks.pop();
                for (const chunk of chunks) {
                    if (chunk.trim() === "") continue;
                    try {
                        const data = JSON.parse(chunk);
                        messages += data.message.content;
                        ollama_chat_result.value += data.message.content;
                        ollama_chat_result.scrollTop = ollama_chat_result.scrollHeight;
                    } catch (e) {
                        console.error("Failed to parse chunk:", chunk);
                    }
                }
            }
            ollamaStreamChatBtn.classList.remove("btnDisabled");
            ollama_chat_result.scrollTop = ollama_chat_result.scrollHeight;
            const ollamaData = {
                "filename": II.tesseract_ocr_result.filename,
                "model": ollama_model_name.value,
                "ai": messages,
                "messages": [...payload.messages]
            }
        }
        const btnGroup = document.getElementsByClassName('btn');
        Array.from(btnGroup).forEach(btn => {
            btn.onclick = function (event) {
                switch (event.target.textContent) {
                    case "OCR":
                        II.blobOcr();
                        showNotification("OCR：" + event.target.textContent, "success");
                        break;
                    case "识别内容":
                        const data = tesseract_ocr_result.value;
                        copyToClipboard(data);
                        break;
                    case "Ollama Chat":
                        ollamaStreamChat();
                        //showNotification("Ollama Chat：" + event.target.textContent, "success");
                        break;
                    default:
                        showNotification("未识别功能：" + event.target.textContent, "error");
                }
            }
        })

        function downloadJson() {
            const data = {
                "ollama-url": document.getElementById("ollama-url").value,
                "ollama-models-name": document.getElementById("ollama-models-name").value,
                "system-prompt": document.getElementById("system-prompt").value,
                "tesseract-ocr": tesseract_ocr_result.value,
                "ollama-chat": document.getElementById("ollama-chat").value
            }
            let fileName = "aiChat_chats_" + "." + Date.now().toString(16) + ".json";
            let jsonString = JSON.stringify(CM.chats, null, 2);
            let blob = new Blob([jsonString], { type: 'application/json' });
            let url = URL.createObjectURL(blob);
            let link = document.createElement('a');
            link.href = url;
            link.setAttribute('download', fileName);
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            showNotification("成功下载文件：" + fileName, "success");
            //console.log("成功下载文件：" + fileName);
        }
    </script>

    <style>
        .zoom-modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            padding: 50px;
            background-color: rgba(0, 0, 0, 0.9);
            justify-content: center;
            align-items: center;
            overflow: auto;
        }

        .zoom-modal-content {
            background-color: white;
            padding: 20px;
            border-radius: 10px;
            width: 300px;
            margin: auto;
            display: block;
            width: 90%;
        }

        .zoom-modal-close {
            position: absolute;
            top: 15px;
            right: 35px;
            color: #f1f1f1;
            font-size: 40px;
            font-weight: bold;
            transition: 0.3s;
            cursor: pointer;
        }

        .zoom-modal-close:hover,
        .zoom-modal-close:focus {
            color: #bbb;
            text-decoration: none;
            cursor: pointer;
        }
    </style>
    <!-- 放大预览的模态框 -->
    <div id="imageModal" class="zoom-modal">
        <span class="zoom-modal-close">&times;</span>
        <img id="expandedImg" class="zoom-modal-content">
    </div>
    <script>
        class PreviewImage {
            constructor() {
                this.modal = document.getElementById('imageModal');
                this.modalImg = document.getElementById('expandedImg');
                this.previewImg = II.img;
                this.closeBtn = document.getElementsByClassName('zoom-modal-close')[0];
                this.elementListener(this);
            }
            elementListener(someVariable) {
                this.previewImg.onclick = function () { // 点击图片显示模态框
                    someVariable.modal.style.display = 'block';
                    someVariable.modalImg.src = this.src;
                }
                this.closeBtn.onclick = function () { // 点击关闭按钮隐藏模态框
                    someVariable.modal.style.display = 'none';
                }
                this.modal.onclick = function (event) { // 点击模态框背景也隐藏模态框
                    if (event.target === someVariable.modal) {
                        someVariable.modal.style.display = 'none';
                    }
                }
                document.onkeydown = function (event) {// 按ESC键关闭模态框
                    if (event.key === 'Escape' && someVariable.modal.style.display === 'block') {
                        someVariable.modal.style.display = 'none';
                    }
                };
            }
        }
        const PI = new PreviewImage();
    </script>

    <style>
        .notification {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 25px;
            border-radius: 6px;
            color: white;
            font-weight: 600;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            transform: translateX(150%);
            transition: transform 0.3s ease;
            z-index: 1000;
            display: flex;
            align-items: center;
        }

        .notification.success {
            background-color: #2ecc71;
        }

        .notification.error {
            background-color: #e74c3c;
        }

        .notification.show {
            transform: translateX(0);
        }

        .notification i {
            margin-right: 10px;
            font-size: 1.2rem;
        }
    </style>
    <div class="notification" id="notification"></div>
    <script>
        function showNotification(message, type) {
            const notification = document.getElementById('notification');
            const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle';
            notification.innerHTML = `<i class="fas ${icon}"></i> ${message}`;
            notification.className = `notification ${type}`;
            notification.classList.add('show');
            setTimeout(() => {
                notification.classList.remove('show');
            }, 5000);
        }
    </script>
</body>

</html>